//==========================================================================
// Copyright (c) 2000-2006,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

//#include <elapi.h>
#include <winhack.h>
#include <ddk.h>
#include "CEventManager.h"

ELAPI _CEvent_New(
        /* [in] */ Boolean manualReset,
        /* [in] */ EventState initialState,
        /* [out] */ IEvent **ppEvent);

ELAPI _CMutex_New(
        /* [out] */ IMutex **ppMutex);

ELAPI _CThread_New(
            PThreadMain pEntry,
            _ELASTOS PVoid pArg,
            ThreadCreationFlags flags,
            IThread **ppThread);

static PEventIsrBlockNode s_pIsrMemBlock = NULL;
static const Int32 c_nOrder = 1;
static DLinkNode g_imFreed;

static DLinkNode *s_pEventDelegateList = NULL;
static DLinkNode s_asynchEventQueueHead;

static IMutex *s_pEveDelegateRW;
static IMutex *s_pAsyEveQueueMutex;
//
static IThread *s_pAsyncThread = NULL;
static IEvent *s_pIAsyncEvent;

static IEventDelegate *GetIEventDelegateFromEventNum(UInt32 uEventNum)
{
    DLinkNode *pHead = &s_pEventDelegateList[EVENT_HASH_INDEX(uEventNum)];
    PEventDelegate ped = (PEventDelegate)(pHead->Next());

    while (ped != (PEventDelegate)pHead) {
        if (ped->uEventNum == uEventNum) {
            return ped->pEveDelegate;
        }
        ped = (PEventDelegate)ped->Next();
    }

    return NULL;
}

Int32 AsynchEventHandler(void *pArg)
{
    PEventNodeBase penBase = NULL;
    IEventDelegate *pEveDel = NULL;
    Boolean bIsrEvent;
    WaitResult wr;

    while (TRUE) {
        while (!s_asynchEventQueueHead.IsEmpty()) {
            penBase = (PEventNodeBase)(s_asynchEventQueueHead.First());
            penBase->Detach();
            bIsrEvent = penBase->bIsrParameter;

            s_pEveDelegateRW->Lock(&wr);
            pEveDel = GetIEventDelegateFromEventNum(penBase->uEventNum);
            if (pEveDel) {
                if (!bIsrEvent) {
                    pEveDel->InvokeEventHandler(*(((PEventNode)penBase)->eventData));
                }
                else {
                    MemoryBuf arg((Byte *)(&(((PEventIsrNode)penBase)->isrpm)),
                        sizeof(EmIsrParameter));
                    pEveDel->InvokeEventHandler(arg);
                }
            }
            s_pEveDelegateRW->Unlock();

            if (!bIsrEvent) {
                PEventNode pen = (PEventNode)penBase;
                if (!pen->eventData->IsNull()) {
                    MemoryBuf::Free(pen->eventData);
                }
                free(pen);
            }
            else {
                s_pAsyEveQueueMutex->Lock(&wr);
                PEventIsrNode pin = (PEventIsrNode)penBase;
                g_imFreed.InsertLast((DLinkNode *)&s_pIsrMemBlock[pin->uNum]);
                s_pAsyEveQueueMutex->Unlock();
            }

        }


        s_pIAsyncEvent->Wait(&wr, NULL);
    }

    return 0;
}

UInt32 CEventManager::AddRef()
{
    LONG nRef = m_cRef.Increment();
    return (UInt32)nRef;
}

UInt32 CEventManager::Release()
{
    LONG nRef = m_cRef.Decrement();

    if (0 == nRef) {
        delete this;
    }
    return nRef;
}

ECode CEventManager::Aggregate(
    AggregateType type, PInterface pObj)
{
    return E_NOT_IMPLEMENTED;
}

ECode CEventManager::GetDomain(
    IInterface **ppObj)
{
    return E_NOT_IMPLEMENTED;
}

PInterface CEventManager::Probe(
    REIID riid)
{
    if (EIID_IInterface == riid) {
        return (IInterface *)(IEventManager *)this;
    }
    else if (EIID_IEventManager == riid) {
        return (IEventManager *)this;
    }
    else if (EIID_IEventHandler == riid) {
        return (IEventHandler *)this;
    }
    else if (EIID_CLASS_INFO == riid) {
        return (IInterface *)&ECLSID_CEventManager;
    }
    else if (EIID_GENERIC_INFO == riid) {
        return NULL;
    }
    else if (EIID_INTERFACE_INFO == riid) {
        return (IInterface *)&EIID_IEventManager;
    }

    return NULL;
}

ECode CEventManager::SendEvent(
    /* [in] */ Int32 uEventNum,
    /* [in] */ const MemoryBuf & ebEventData)
{
    WaitResult wr;
    s_pEveDelegateRW->Lock(&wr);

    IEventDelegate *pEveDel = GetIEventDelegateFromEventNum(uEventNum);
    if (pEveDel) {
        pEveDel->InvokeEventHandler(ebEventData);
    }

    s_pEveDelegateRW->Unlock();

    return NOERROR;
}

ECode CEventManager::PostEvent(
    /* [in] */ Int32 uEventNum,
    /* [in] */ Int32 nPriority,
    /* [in] */ const MemoryBuf & ebEventData)
{
    //save the event num
    PEventNode pEventNode = (PEventNode)malloc(sizeof(EventNode));
    if (NULL == pEventNode) return E_OUT_OF_MEMORY;
    memset(pEventNode, 0, sizeof(EventNode));
    pEventNode->uEventNum = uEventNum;
    pEventNode->bIsrParameter = FALSE;

    //save the event data
    pEventNode->eventData = NULL;
    if (!ebEventData.IsNull()) {
        Int32 nDataLen = ebEventData.GetUsed();
        pEventNode->eventData = MemoryBuf::Alloc(nDataLen + 1);
        if (pEventNode->eventData->IsNull()) {
            delete pEventNode;
            return E_OUT_OF_MEMORY;
        }
        pEventNode->eventData->Copy((const Byte *)ebEventData.GetPayload(), nDataLen);
        pEventNode->eventData->SetUsed(nDataLen);
    }

    //put into the async event queue
    s_asynchEventQueueHead.InsertLast((DLinkNode *)((PEventNodeBase)pEventNode));

    s_pIAsyncEvent->Notify(SIGNALED);
    s_pIAsyncEvent->Clear();
    return NOERROR;
}

ECode CEventManager::AddEventHandler(
    /* [in] */ UInt32 uEventNum,
    /* [in] */ Int32 nOrder,
    /* [in] */ IEventDelegate * pDelegate)
{
    if (NULL == pDelegate) return E_INVALID_ARGUMENT;

    PEventDelegate ped = new EventDelegate;
    if (NULL == ped) {
        return E_OUT_OF_MEMORY;
    }

    ped->pEveDelegate = pDelegate;
    ped->pEveDelegate->AddRef();

    ped->uEventNum = uEventNum;

    //insert the event into the event pool
    WaitResult wr;
    s_pEveDelegateRW->Lock(&wr);
    s_pEventDelegateList[EVENT_HASH_INDEX(uEventNum)].InsertFirst(ped);
    s_pEveDelegateRW->Unlock();

    return NOERROR;
}

ECode CEventManager::RemoveEventHandler(
    /* [in] */ UInt32 uEventNum,
    /* [in] */ IEventDelegate * pDelegate)
{
    WaitResult wr;
    s_pEveDelegateRW->Lock(&wr);
    DLinkNode *pHead = &s_pEventDelegateList[EVENT_HASH_INDEX(uEventNum)];
    PEventDelegate ped = (PEventDelegate)(pHead->Next());
    while (ped != (PEventDelegate)pHead) {
        if (ped->uEventNum == uEventNum) {
            ped->Detach();
            s_pEveDelegateRW->Unlock();
            if (ped->pEveDelegate) {
                ped->pEveDelegate->Release();
            }
            delete ped;
            return NOERROR;
        }
        ped = (PEventDelegate)ped->Next();
    }
    s_pEveDelegateRW->Unlock();

    return NOERROR;
}

ECode CEventManager::Initialize()
{
    s_pEventDelegateList = new DLinkNode[EVENT_HASH_LENGTH];
    if (NULL == s_pEventDelegateList) {
        return E_OUT_OF_MEMORY;
    }

    UInt32 i;
    for (i = 0; i < EVENT_HASH_LENGTH; i++) {
        s_pEventDelegateList[i].Initialize();
    }

    s_asynchEventQueueHead.Initialize();

    _CMutex_New(&s_pAsyEveQueueMutex);

    _CMutex_New(&s_pEveDelegateRW);

    _CEvent_New(TRUE, UNSIGNALED, &s_pIAsyncEvent);

    ECode ec = _CThread_New(AsynchEventHandler, (PVoid)this,
                        0, &s_pAsyncThread);
    if (FAILED(ec)) {
        delete s_pEventDelegateList;
        return ec;
    }

    s_pIsrMemBlock = (PEventIsrBlockNode)DzAllocKernelPages(c_nOrder);
    if (NULL == s_pIsrMemBlock) {
        delete s_pEventDelegateList;
        s_pAsyncThread->Release();
        return E_OUT_OF_MEMORY;
    }

    g_imFreed.Initialize();
    UInt32 mbNum = ((1u << c_nOrder) * PAGE_SIZE) / sizeof(EventIsrBlockNode);
    for (i = 0; i < mbNum; i++) {
        s_pIsrMemBlock[i].eventIsrNode.bIsrParameter = TRUE;
        s_pIsrMemBlock[i].eventIsrNode.uNum = i;
        g_imFreed.InsertLast((DLinkNode *)&s_pIsrMemBlock[i]);
    }

    return NOERROR;
}

CEventManager::~CEventManager()
{
    s_pAsyncThread->Release();

    PEventNodeBase pbn = NULL;
    while (!s_asynchEventQueueHead.IsEmpty()) {
        pbn = (PEventNodeBase)s_asynchEventQueueHead.First();
        pbn->Detach();
        if (!pbn->bIsrParameter) {
            delete ((PEventNode)pbn);
        }
    }

    UInt32 i;
    PEventDelegate ped;
    for (i = 0; i < EVENT_HASH_LENGTH; i++) {
        while (!s_pEventDelegateList[i].IsEmpty()) {
            ped = (PEventDelegate)(s_pEventDelegateList[i].First());
            ped->Detach();
            delete ped;
        }
    }

    delete s_pEventDelegateList;

    DzFreeKernelPages(s_pIsrMemBlock, c_nOrder);
}

EXTERN ECode EmPostEventByIsr(
    /* [in] */ UInt32 uEventNum,
    /* [in] */ PEmIsrParameter pEmIsrParm)
{
    if (!g_imFreed.IsEmpty()) {
        PEventIsrBlockNode peb = (PEventIsrBlockNode)g_imFreed.First();
        peb->Detach();
        peb->eventIsrNode.uEventNum = uEventNum;
        memcpy((PVoid)&(peb->eventIsrNode.isrpm), pEmIsrParm, sizeof(EmIsrParameter));
        s_asynchEventQueueHead.InsertLast((DLinkNode *)((PEventNodeBase)&(peb->eventIsrNode)));
    }

    s_pIAsyncEvent->Notify(SIGNALED);
    s_pIAsyncEvent->Clear();

    return NOERROR;
}
